﻿using System;
using System.Data;
using System.Reflection;
using System.Web;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Configuration; 
using System.Web.Routing;
using System.Runtime.Serialization;

namespace Sharp
{
    /// <summary>
    /// Enforces a RESTful API
    /// </summary>
    /// <typeparam name="GET">Return Type</typeparam>
    /// <typeparam name="get">Parameter Type</typeparam>
    /// <typeparam name="POST">Return Type</typeparam>
    /// <typeparam name="post">Parameter Type</typeparam>
    /// <typeparam name="PUT">Return Type</typeparam>
    /// <typeparam name="put">Parameter Type</typeparam>
    /// <typeparam name="DELETE">Return Type</typeparam>
    /// <typeparam name="delete">Parameter Type</typeparam>
    public interface IREST<GET, get,POST,post,PUT,put,DELETE,delete>
    {
        GET Get(get value);
        POST Post(post value);
        PUT Put(put value);
        DELETE Delete(delete value);
    }

    [DataContract]
    public class Handler : Web, IDisposable, IHttpHandler
    {
        private String path { get; set; }
        protected HttpHandler.ContentType responseType { get; set; }
        protected HttpHandler.ContentType contentType { get; set; }
        private HttpHandler.HTTPVerb verb { get; set; }
        private bool isTest { get; set; }
        [DataMember]
        public object data { get; set; }
        public MethodInfo method { get; set; }
        public object handler { get; set; }

        public Template template { get; set; }

        public delegate void HandlerEvent(HttpContext context);
        public event HandlerEvent preProcessRequestEvent;
                
        public delegate void preDisposeHandler();
        public event preDisposeHandler onDispose;


        public Handler()
        {
            contentType = context.Request.AcceptTypes.Count(x => x.LikeOne(new string[] { "application/json", "text/javascript" })) > 0 ? HttpHandler.ContentType.JSON : HttpHandler.ContentType.HTML;  // ((HttpHandler.ContentType)(RouteTable.Routes.GetRouteData((HttpContextBase)new HttpContextWrapper(context)).Values["content-type"] ?? HttpHandler.ContentType.HTML));
            verb = ((HttpHandler.HTTPVerb)(RouteTable.Routes.GetRouteData((HttpContextBase)new HttpContextWrapper(context)).Values["verb"] ?? HttpHandler.ContentType.HTML));
            SetContentType();
            
            CheckForTest();
        }

        public Handler(HttpHandler.ContentType contentType, HttpHandler.HTTPVerb vrb)
        {
            this.contentType = contentType;
            verb = vrb;
            SetContentType();
            
            CheckForTest();
        }

        private void SetContentType()
        {
            if (contentType == HttpHandler.ContentType.JSON)
                context.Response.ContentType = "application/json";
            else if (contentType == HttpHandler.ContentType.HTML)
                context.Response.ContentType = "text/html";
            else
                throw new Exception(string.Format("content type {0} not implemented", this.contentType.ToStringValue()));
        }

        private void CheckForTest()
        {
            isTest = context.Request.QueryString["test"] != null;
            if (isTest) sb.ErrorMsg = "Testing"; //error state to prevent DB commits

            path = "/" + context.Request.Path.ToLower().Trim(new char[] { '/' });
            //method = path.Substring(path.LastIndexOf("/") + 1);
        }

        public Handler(string TemplatePath)
        {
            template = new Template(TemplatePath);
            context.Response.ContentType = "text/html";
        }

        public void RenderTestForm(string _class)
        {
            this.contentType = HttpHandler.ContentType.HTML;
            //gen test form
            StringBuilder sb = new StringBuilder();
            template = new Template(Resources.Read["api.htm"]);

            String Field = @"<label>{0}</label>{1}<br /><input name=""{0}"" placeholder=""{2}"" value=""[%{0}%]"" /><br />";

            template.Set("RequestType", verb.ToString());
            template.Set("URL", context.Request.Path);

            if (method == null && HttpContext.Current.Items["method"] != null)
            {
                method = (MethodInfo)HttpContext.Current.Items["method"];
            }

            if (method != null)
            {
                foreach (var parms in method.GetParameters())
                {
                    sb.AppendFormat(Field, parms.Name, parms.IsOptional ? " - Optional, default (" + parms.DefaultValue.ToString() + ")" : "", parms.DefaultValue ?? "");
                }
                template.ReplaceTag("fields", sb.ToString());
            }
            else
            {
                Type handlerType;

                if (handler == null)
                {
                    handlerType = (Type)HttpContext.Current.Items["handler"];
                }
                else
                {
                    handlerType = handler.GetType();
                }

                foreach (Object attr in handlerType.GetCustomAttributes(typeof(HttpHandler), false))
                {
                    //Gen Form Fields
                    if (((HttpHandler)attr).Parameters != null)
                    {
                        foreach (String str in ((HttpHandler)attr).Parameters)
                        {
                            sb.AppendFormat(Field, str);
                        }
                        template.Set("fields", sb.ToString());
                    }
                }
            }
        }


        public bool IsReusable
        { get { return false; } }  

        public virtual void ProcessRequest(HttpContext context)
        {
            if (preProcessRequestEvent != null)
                preProcessRequestEvent(context);



            if (!isTest && verb != HttpHandler.HTTPVerb.ALL && !verb.ToStringValue().Like(context.Request.RequestType))
            {
                throw new System.Web.HttpException(404, "No http handler was found for request type 'GET'");
            }

            if (isTest)
            {
                RenderTestForm(context.CurrentHandler.ToString());
            }
            
            if (!isTest && method != null && this.handler != null) //Run Action TODO find a faster way to deserialize that doesn't invovle all the foreach loops
            {
                //build objects to be passed into the method from query or form data
                //List<Object> parms = new List<object>();
                Dictionary<string, object> dict = new Dictionary<string, object>();
                var methodParameters = method.GetParameters(); 
                foreach (var methodParam in methodParameters)
                {  
                    string value = Form[methodParam.Name].Else(Query[methodParam.Name]); 
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        var newparm = typeof(JSONHelper).GetMethod("ParseJSON").MakeGenericMethod(new Type[] { methodParam.ParameterType }).Invoke(this, new Object[] { value });
                        dict.Add(methodParam.Name, newparm);
                    }
                    else if (methodParam.IsOptional) //add default value
                    {
                        dict.Add(methodParam.Name, methodParam.DefaultValue);
                    }
                }

                if (dict.Count == methodParameters.Length)
                { 
                    //Store output of action "method"
                    var tempdata = method.Invoke(handler, dict.Select(x=>x.Value).ToArray()); 
					if (data == null && tempdata != null) this.data = tempdata;
                }
                else
                { 
                    throw new Exception("Missing " + (methodParameters.Length - dict.Count) + " parameters needed to invoke this method: \n\n" + String.Join("\n", methodParameters.Where(x=> !x.Name.LikeOne(dict.Select(y=>y.Key).ToArray())).Select(x => x.ParameterType.ToString() + " " + x.Name).ToArray()) + "\n\n");
                }
            }
            
            if (template != null) // Pump out template
            {
                sb.Reset(this.template);

                template.Set("currenttime", DateTime.Now.ToString("M/d - h:mmtt").ToLower());

                if (Query["Error"].NNOE()) template.Error = Cached.GetFile("/a/msg/" + Query["Error"].ToInt(0).ToString() + ".htm"); //convert to int to prevent abuse  
                if (Query["Warning"].NNOE()) template.Warning = Cached.GetFile("/a/msg/" + Query["Warning"].ToInt(0).ToString() + ".htm");
                if (Query["Msg"].NNOE()) template.Msg = Cached.GetFile("/a/msg/" + Query["Msg"].ToInt(0).ToString() + ".htm");

                template.Set("domain", context.Request.Url.Host);
                template.Set("like", context.Request.Url.ToString().ToURL()); 

                try
                {
                    context.Response.AppendHeader("ServeTime", (DateTime.Now.Subtract((DateTime)context.Items["Time"])).TotalMilliseconds.ToString() + "ms");
                    context.Response.AppendHeader("Queries", context.Items["Queries"].ToString());
                }
                catch { }
            
                this.data = template.ToString();
            }
            
            Dispose(); 
            
        } //write the template to output } 

       
        
    
        public void Dispose()
        {
            //Run dispose delegate otherwise write to output stream based on content type
            if (onDispose != null)
                onDispose();
            else
            {

                context.Response.Expires = 0; 
                {
                    if (contentType == HttpHandler.ContentType.JSON)
                    {
                        Write(this.ToJSON());
                    }
                    else if (contentType == HttpHandler.ContentType.HTML)
                    { 
                        if (this != null)
                        { 
                            Write(this.data);

                            if (this.data == null)
                                foreach (string error in sb.Errors)
                                    Write(error);
                        } 
                    }
                    else
                    { //serialize to XML ;
                        throw new Exception("content type not implemented");
                    }
                }
            }

            // If any transactions failed to close then roll them all back.
            if (sb.Tn != null)
                sb.Tn.Rollback();

            // The end. (if it even get here)
            //context.Response.Flush(); // causes extra stuff to be output on nginx!
            context.Response.End();

        }


        
    }


}
